﻿/*
 * Created by SharpDevelop.
 * User: issuser
 * Date: 2010-12-9
 * Time: 14:34
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Data;
using NLite.Log;
using System.Collections.Generic;
using NLite.Data.Driver.Internal;
using System.Data.Common;
using NLite.Reflection;
using System.Linq;
using System.Diagnostics;
using NLite.Data.Schema;
using NLite.Data.Schema.Internal;
using NLite.Collections;
using System.Collections;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using NLite.Data.SqlTypes;

namespace NLite.Data.Driver
{
    /// <summary>
    /// Description of DriverBase.
    /// </summary>
    public abstract class DriverBase : IDriver
    {
        public ISchemaProvider SchemaProvider { get; set; }

        public abstract string NamedPrefix { get; }
        public virtual bool SupportsPreparingCommands { get { return true; } }
        public virtual bool UseNamedPrefixInParameter { get { return true; } }
        public virtual bool UseNamedPrefixInSql { get { return true; } }
        public virtual int UserParametersStartIndex { get { return 1; } }

        public virtual bool SupportsMultipleOpenReaders { get { return false; } }
        public virtual bool SupportsMultipleQueries
        {
            get { return true; }
        }
        public virtual string MultipleQueriesSeparator
        {
            get { return ";"; }
        }
        public virtual bool SupportsCollectionOperation { get { return true; } }
        public virtual bool SupportsSubQuery { get { return true; } }

        public string ConnectionString { get; set; }
        public int? CommandTimeout { get; set; }
        public bool IsPrepareSqlEnabled { get; set; }

        public void PrepareCommand(System.Data.IDbCommand cmd, System.Data.CommandType type, string sqlString, params IDataParameter[] ps)
        {
            cmd.CommandType = type;

            SetCommandText(sqlString, cmd);
            SetCommandTimeout(cmd);
            SetCommandParameters(cmd, ps);

            if (IsPrepareSqlEnabled)
            {
                try
                {
                    cmd.Prepare();
                }
                finally
                {
                }
            }
           
        }

        private void SetCommandParameters(System.Data.IDbCommand cmd, IDataParameter[] ps)
        {
            if (ps != null && ps.Length > 0)
                foreach (var p in ps)
                    GenerateParameter(cmd, p);
        }

        private void SetCommandTimeout(System.Data.IDbCommand cmd)
        {
            if (CommandTimeout.HasValue && CommandTimeout.Value > 0)
            {
                cmd.CommandTimeout = CommandTimeout.Value;
            }
        }

        static void SetCommandText(string sqlString, System.Data.IDbCommand cmd)
        {
            cmd.CommandText = sqlString;
            Console.WriteLine(sqlString);
        }

        public System.Data.IDbDataParameter GenerateParameter(System.Data.IDbCommand command, IDataParameter p)
        {
            var cp = command.CreateParameter(p.Name,p.Value);
            InitializeParameter(cp, p);
            command.Parameters.Add(cp);
            return cp;
        }

        protected virtual void InitializeParameter(System.Data.IDbDataParameter cp, IDataParameter p)
        {
            if (p.Identity == null)
                return;

            cp.Direction = p.Identity.Direction;

            switch (p.Identity.Type.DbType)
            {
                case SqlTypes.DbTypeCode.Clob:
                    cp.DbType = System.Data.DbType.AnsiString;
                    break;
                case SqlTypes.DbTypeCode.NClob:
                    cp.DbType = System.Data.DbType.String;
                    break;
                case SqlTypes.DbTypeCode.Blob:
                    cp.DbType = System.Data.DbType.Binary;
                    break;
                default:
                    cp.DbType = (DbType)p.Identity.Type.DbType;
                    break;
            }

        }

        public DbConnection CreateConnection()
        {
            var conn = InternalCreateConnection();
            conn.ConnectionString = ConnectionString;
            return new AdoConnection(conn,this);
        }

        protected abstract DbConnection InternalCreateConnection();
        
        
    }

    namespace Internal
    {
        [Serializable]
        class AdoConnection : DbConnection
        {
            private static readonly ILog log = LogManager.GetLogger(typeof(AdoConnection));

            [NonSerialized]
            private DbConnection connection;
            private IDriver Driver;

            public override DataTable GetSchema()
            {
                return PopulateTable(connection.GetSchema());
            }

            private DataTable PopulateTable(DataTable dataTable)
            {
                if (dataTable == null) return null;
                dataTable.Columns.Cast<DataColumn>().ForEach(p =>p.ColumnName = FormateColumnName(p.ColumnName));
                return dataTable;
            }

            private static string FormateColumnName(string p)
            {
                return p.Split('_').Select(s=>s.ToLower().FirstUpper()).ToCSV("");
            }

            public override DataTable GetSchema(string collectionName)
            {
                return PopulateTable(connection.GetSchema(collectionName));
            }

            public override DataTable GetSchema(string collectionName, string[] restrictionValues)
            {
                return PopulateTable(connection.GetSchema(collectionName, restrictionValues));
            }

            public override void EnlistTransaction(System.Transactions.Transaction transaction)
            {
                connection.EnlistTransaction(transaction);
            }

            protected override DbProviderFactory DbProviderFactory
            {
                get
                {
                    return connection.GetProperty<DbProviderFactory>("DbProviderFactory");
                }
            }

            public AdoConnection(DbConnection conn, IDriver driver)
            {
                connection = conn;
                Driver = driver;
            }

            public override string ConnectionString
            {
                get
                {
                    return connection.ConnectionString;
                }
                set
                {
                    connection.ConnectionString = value;
                }
            }

            public override string Database
            {
                get { return connection.Database; }
            }

            public override ConnectionState State
            {
                get { return connection.State; }
            }

            public override int ConnectionTimeout
            {
                get { return connection.ConnectionTimeout; }
            }

            public override string DataSource
            {
                get { return connection.DataSource; }
            }

            public override string ServerVersion
            {
                get { return connection.ServerVersion; }
            }

            protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
            {
                try
                {
                    return new AdoTransaction(connection.BeginTransaction(isolationLevel));
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    throw new DAOException(ex.Message, ex);
                }
            }

            public override void ChangeDatabase(string databaseName)
            {
                connection.ChangeDatabase(databaseName);
            }

            public override void Open()
            {
                try
                {
                    connection.Open();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    throw new DAOException(ex.Message, ex);
                }
            }

            protected override DbCommand CreateDbCommand()
            {
                return new AdoCommand(connection.CreateCommand(), Driver);
            }
            
            public override void Close()
            {
                try
                {
                    connection.Close();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    throw new DAOException(ex.Message, ex);
                }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    connection.Dispose();
                }
            }
            
        }

        class FieldNameLookup
        {
            private readonly Dictionary<string, int?> Cache;

            public FieldNameLookup(int columnCount)
            {
                Cache = new Dictionary<string, int?>(columnCount);
            }

            public int GetIndexForFieldName(string columnName, IDataReader rs)
            {
                int? cached;
                Cache.TryGetValue(columnName, out cached);
                if (cached.HasValue)
                {
                    return cached.Value;
                }
                else
                {
                    int index = rs.GetOrdinal(columnName);
                    Cache[columnName] = index;
                    return index;
                }
            }
        }

        class AdoReader : DbDataReader
        {
            private readonly DbDataReader rs;
            private readonly FieldNameLookup FieldLookup;

            public AdoReader(DbDataReader resultSet)
            {
                rs = resultSet;
                this.FieldLookup = new FieldNameLookup(rs.FieldCount);
            }

            internal IDataReader Target
            {
                get { return rs; }
            }

            public override bool HasRows
            {
                get { return rs.HasRows; }
            }
            #region IDataReader Members

            public override void Close()
            {
                rs.Close();
            }

            public override DataTable GetSchemaTable()
            {
                return rs.GetSchemaTable();
            }

            public override bool NextResult()
            {
                return rs.NextResult();
            }

            public override bool Read()
            {
                return rs.Read();
            }

            public override int Depth
            {
                get { return rs.Depth; }
            }

            public override bool IsClosed
            {
                get { return rs.IsClosed; }
            }

            public override int RecordsAffected
            {
                get { return rs.RecordsAffected; }
            }

            public override int VisibleFieldCount
            {
                get
                {
                    return rs.VisibleFieldCount;
                }
            }
            #endregion

    

            #region IDataRecord Members

            public override string GetName(int i)
            {
                return rs.GetName(i);
            }

            public override string GetDataTypeName(int i)
            {
                return rs.GetDataTypeName(i);
            }

            public override System.Type GetFieldType(int i)
            {
                return rs.GetFieldType(i);
            }

            public override object GetValue(int i)
            {
                return rs.GetValue(i);
            }

            public override int GetValues(object[] values)
            {
                return rs.GetValues(values);
            }

            public override int GetOrdinal(string name)
            {
                return FieldLookup.GetIndexForFieldName(name, this);
            }

            public override bool GetBoolean(int i)
            {
                return rs.GetBoolean(i);
            }

            public override byte GetByte(int i)
            {
                return rs.GetByte(i);
            }

            public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
            {
                return rs.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
            }

            public override char GetChar(int i)
            {
                return rs.GetChar(i);
            }

            public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
            {
                return rs.GetChars(i, fieldoffset, buffer, bufferoffset, length);
            }

            public override Guid GetGuid(int i)
            {
                return rs.GetGuid(i);
            }

            public override short GetInt16(int i)
            {
                return rs.GetInt16(i);
            }

            public override int GetInt32(int i)
            {
                return rs.GetInt32(i);
            }

            public override long GetInt64(int i)
            {
                return rs.GetInt64(i);
            }

            public override float GetFloat(int i)
            {
                return rs.GetFloat(i);
            }

            public override double GetDouble(int i)
            {
                return rs.GetDouble(i);
            }

            public override string GetString(int i)
            {
                return rs.GetString(i);
            }

            public override decimal GetDecimal(int i)
            {
                return rs.GetDecimal(i);
            }

            public override DateTime GetDateTime(int i)
            {
                return rs.GetDateTime(i);
            }


            public override bool IsDBNull(int i)
            {
                return rs.IsDBNull(i);
            }

            public override int FieldCount
            {
                get { return rs.FieldCount; }
            }

            public override object this[int i]
            {
                get { return rs[i]; }
            }

            public override object this[string name]
            {
                get { return rs[FieldLookup.GetIndexForFieldName(name, this)]; }
            }

            #endregion

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    rs.Dispose();
                }
            }

            public override System.Collections.IEnumerator GetEnumerator()
            {
                return rs.GetEnumerator();
            }
        }

        class AdoTransaction : DbTransaction, ITransaction
        {
            private static readonly ILog log = LogManager.GetLogger(typeof(AdoTransaction));
            private DbTransaction trans;
            private bool committed;
            private bool rolledBack;
            private bool commitFailed;

            public AdoTransaction(DbTransaction trans)
            {
                this.trans = trans;

                committed = false;
                rolledBack = false;
            }


            public bool WasRolledBack
            {
                get { return rolledBack; }
            }


            public bool WasCommitted
            {
                get { return committed; }
            }

            public bool IsActive
            {
                get { return !rolledBack && !committed; }
            }

            public override IsolationLevel IsolationLevel
            {
                get { return trans.IsolationLevel; }
            }

            protected override DbConnection DbConnection
            {
                get { return trans.Connection; }
            }
           

            public override void Commit()
            {

                //CheckNotDisposed();
                CheckNotZombied();

                log.Debug("Start Commit");

                try
                {
                    trans.Commit();
                    log.Debug("IDbTransaction Committed");

                    committed = true;

                }
                catch (Exception e)
                {
                    log.Error("Commit failed", e);
                    commitFailed = true;
                    throw new DAOException("Commit failed with SQL exception", e);
                }
                finally
                {
                    Dispose();
                }
            }
            public override void Rollback()
            {
                //CheckNotDisposed();
                CheckNotZombied();

                log.Debug("Rollback");

                if (!commitFailed)
                {
                    try
                    {
                        trans.Rollback();
                        log.Debug("IDbTransaction RolledBack");
                        rolledBack = true;
                    }
                    catch (Exception e)
                    {
                        log.Error("Rollback failed", e);
                        throw new DAOException("Rollback failed with SQL Exception", e);
                    }
                    finally
                    {
                        Dispose();
                    }
                }
            }

            public void Enlist(IDbCommand command)
            {
                if (trans == null)
                {
                    if (log.IsWarnEnabled)
                    {
                        if (command.Transaction != null)
                        {
                            log.Warn("set a nonnull IDbCommand.Transaction to null because the Session had no Transaction");
                        }
                    }

                    command.Transaction = null;
                    return;
                }
                else
                {
                    if (log.IsWarnEnabled)
                    {
                        if (command.Transaction != null && command.Transaction != trans)
                        {
                            log.Warn("The IDbCommand had a different Transaction than the Session.  This can occur when " +
                                     "Disconnecting and Reconnecting Sessions because the PreparedCommand Cache is Session specific.");
                        }
                    }
                    log.Debug("Enlist Command");

                    command.Transaction = trans;
                }
            }

            private void CheckNotZombied()
            {
                if (trans != null && trans.Connection == null)
                {
                    throw new DAOException("Transaction not connected, or was disconnected");
                }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    trans.Dispose();
                    trans = null;
                }
            }

        }

        class AdoCommand : DbCommand,IDbCommand
        {
            private static readonly ILog log = LogManager.GetLogger(typeof(AdoCommand));

            private DbCommand cmd;
            private IDriver driver;
            public AdoCommand(DbCommand cmd, IDriver driver)
            {
                this.cmd = cmd;
                this.driver = driver;
            }
            public override bool DesignTimeVisible
            {
                get
                {
                    return cmd.DesignTimeVisible;
                }
                set
                {
                    cmd.DesignTimeVisible = value;
                }
            }

            public override void Cancel()
            {
                try
                {
                    cmd.Cancel();
                }
                catch (Exception e)
                {
                    log.Error(e.Message, e);
                    throw new DAOException(e.Message, e);
                }
            }

            public override string CommandText
            {
                get
                {
                    return cmd.CommandText;
                }
                set
                {
                    cmd.CommandText = value;
                }
            }

            public override int CommandTimeout
            {
                get
                {
                    return cmd.CommandTimeout;
                }
                set
                {
                    try
                    {
                        cmd.CommandTimeout = value;
                    }
                    catch (Exception e)
                    {
                    }
                }
            }

            public override CommandType CommandType
            {
                get
                {
                    return cmd.CommandType;
                }
                set
                {
                    cmd.CommandType = value;
                }
            }

            protected override DbConnection DbConnection
            {
                get
                {
                    return cmd.Connection;
                }
                set
                {
                    cmd.Connection = value;
                }
            }

            protected override DbTransaction DbTransaction
            {
                get
                {
                    return cmd.Transaction;
                }
                set
                {
                    if (value != null && !(value is AdoTransaction))
                        cmd.Transaction = new AdoTransaction(value);
                    else
                        cmd.Transaction = value;
                }
            }
            protected override DbParameter CreateDbParameter()
            {
                return cmd.CreateParameter();
            }

            //IDbDataParameter IDbCommand.CreateParameter()
            //{
            //    return (IDbDataParameter)cmd.CreateParameter(null, null);
            //}

            //IDataParameterCollection IDbCommand.Parameters
            //{
            //    get { return cmd.Parameters; }
            //}

            public override int ExecuteNonQuery()
            {
                try
                {
                    return cmd.ExecuteNonQuery();
                }
                catch (Exception e)
                {
                    log.Error(e.Message, e);
                    throw new DAOException(e.Message, e);
                }
            }

            protected override DbParameterCollection DbParameterCollection
            {
                get { return cmd.Parameters; }
            }

            protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
            {
                try
                {
                    return new AdoReader(cmd.ExecuteReader(behavior));
                }
                catch (Exception e)
                {
                    log.Error(e.Message, e);
                    throw new DAOException(e.Message, e);
                }
            }

          

            public override object ExecuteScalar()
            {
                try
                {
                    return cmd.ExecuteScalar();
                }
                catch (Exception e)
                {
                    log.Error(e.Message, e);
                    throw new DAOException(e.Message, e);
                }
            }

            public override void Prepare()
            {
                if (driver.SupportsPreparingCommands)
                {
                    try
                    {
                        cmd.Prepare();
                    }
                    catch (Exception e)
                    {
                        log.Error(e.Message, e);
                        throw new DAOException(e.Message, e);
                    }
                }
            }

          

            public override UpdateRowSource UpdatedRowSource
            {
                get
                {
                    return cmd.UpdatedRowSource;
                }
                set
                {
                    cmd.UpdatedRowSource = value;
                }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    cmd.Dispose();
            }
        }

        class OracleConnection : System.Data.Common.DbConnection
        {
            static readonly DataSet Metadata;
            static readonly Dictionary<string, List<MetadataInfo>> MetadataCollections = new Dictionary<string, List<MetadataInfo>>();
            static readonly ILog log = LogManager.GetLogger(typeof(OracleConnection));

            class MetadataInfo
            {
                public string CollectionName;
                public int NumberOfRestrictions;
                public string PopulationMechanism;
                public string PopulationString;
                public string MinimumVersion;
                public string MaximumVersion;

                public double? MinVersion;
                public double? MaxVersion;
            }

            static OracleConnection()
            {
                Metadata = new DataSet();
                Metadata.ReadXml(Assembly.GetExecutingAssembly().GetManifestResourceStream("NLite.Data.Driver.OracleMetaData.xml"));


                var metadatas = Metadata
                    .Tables[DbMetaDataCollectionNames.MetaDataCollections]
                    .ToList<MetadataInfo>()
                    .Where(p => p.PopulationMechanism == "SQLCommand")
                    .Select(p=>
                        {
                            var items = p.PopulationString.Matches(':');
                            var length = items.Length;
                            for (var i = 0; i < length; i++)
                                p.PopulationString = p.PopulationString.Replace(":" + items[i], ":p" + i.ToString());
                            if (!string.IsNullOrEmpty(p.MaximumVersion))
                                p.MaxVersion = double.Parse( p.MaximumVersion.Split('.').Take(2).ToCSV("."));
                            if (!string.IsNullOrEmpty(p.MinimumVersion))
                                p.MinVersion = double.Parse(p.MinimumVersion.Split('.').Take(2).ToCSV("."));
                            p.CollectionName = p.CollectionName.ToUpper();
                            return p;
                        });

                foreach (var item in metadatas)
                {
                    if (!MetadataCollections.ContainsKey(item.CollectionName))
                        MetadataCollections[item.CollectionName] = new List<MetadataInfo>();

                    var items = MetadataCollections[item.CollectionName];
                    items.Add(item);
                }
                    
            }

            private IDbConnection Connection;
            private string UserId;
            private double? Version;
            public OracleConnection(IDbConnection conn)
            {
                Connection = conn;
                UserId = ParseOracleUserId(conn.ConnectionString);
            }

            private string ParseOracleUserId(string connString)
            {
                if (string.IsNullOrEmpty(connString))
                    return null;
                return connString
                    .Split(';')
                    .Select(p => p.Split('='))
                    .Where(p => p.Length == 2)
                    .Where(p => string.Equals(p[0].Trim(), "User Id", StringComparison.InvariantCultureIgnoreCase))
                    .Select(p => p[1])
                    .FirstOrDefault();
            }

            protected override System.Data.Common.DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
            {
                return new OracleTransaction(Connection.BeginTransaction(isolationLevel));
            }

            public override void ChangeDatabase(string databaseName)
            {
                Connection.ChangeDatabase(databaseName);
            }

            public override void Close()
            {
                Connection.Close();
                Version = null;
            }

            public override string ConnectionString
            {
                get
                {
                    return Connection.ConnectionString;
                }
                set
                {
                    Connection.ConnectionString = value;
                    UserId = ParseOracleUserId(value);
                }
            }

            protected override System.Data.Common.DbCommand CreateDbCommand()
            {
                var cmd = Connection.CreateCommand();
                return new OracleCommand(cmd);
            }

            public override string DataSource
            {
                get { return Connection.GetProperty<string>("DataSource"); }
            }

            public override string Database
            {
                get { return UserId; }
            }

            public override void Open()
            {
                Connection.Open();
                Version = double.Parse(ServerVersion.Split('.').Take(2).ToCSV("."));
            }

            public override string ServerVersion
            {
                get { return Connection.GetProperty<string>("ServerVersion"); }
            }

            public override ConnectionState State
            {
                get { return Connection.State; }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    Connection.Dispose();
            }

            public override DataTable GetSchema()
            {
                return GetSchema(DbMetaDataCollectionNames.MetaDataCollections);
            }

            public override DataTable GetSchema(string collectionName)
            {
                return GetSchema(collectionName, null);
            }

            public override DataTable GetSchema(string collectionName, string[] restrictionValues)
            {
                var collName = collectionName.ToUpper();
                if (string.Equals(DbMetaDataCollectionNames.MetaDataCollections.ToUpper(), collName))
                    return Metadata.Tables[DbMetaDataCollectionNames.MetaDataCollections];
                if((string.Equals(DbMetaDataCollectionNames.DataSourceInformation.ToUpper(), collName)))
                    return Metadata.Tables[DbMetaDataCollectionNames.DataSourceInformation];
                if ((string.Equals(DbMetaDataCollectionNames.DataTypes.ToUpper(), collName)))
                    return Metadata.Tables[DbMetaDataCollectionNames.DataTypes];
                 if ((string.Equals(DbMetaDataCollectionNames.ReservedWords.ToUpper(), collName)))
                    return Metadata.Tables[DbMetaDataCollectionNames.ReservedWords];
                if ((string.Equals(DbMetaDataCollectionNames.Restrictions.ToUpper(), collName)))
                    return Metadata.Tables[DbMetaDataCollectionNames.Restrictions];

                if(!MetadataCollections.ContainsKey(collName))
                    throw new InvalidOperationException();

                MetadataInfo item = GetMetadata(collName);

                return this.ExecuteReader(
                                          item.PopulationString//sql
                                          , GetRestrictionValues(restrictionValues, item))
                    .ToDataTable();
            }

            private static string[] GetRestrictionValues(string[] restrictionValues, MetadataInfo item)
            {
                if (restrictionValues == null)
                    return Enumerable.Range(1, item.NumberOfRestrictions)
                        .Select(p => (string)null).ToArray();

                if (item.NumberOfRestrictions == restrictionValues.Length)
                    return restrictionValues;

                var conditions = new string[item.NumberOfRestrictions];
                restrictionValues.CopyTo(conditions, 0);
                return conditions;
            }

            private MetadataInfo GetMetadata(string collectionName)
            {
                var items = MetadataCollections[collectionName];
                if (items.Count == 1)
                    return items[0];

                var tmpItem = items.OrderByDescending(p => p.MaxVersion).FirstOrDefault(p => Version >= p.MaxVersion);
                var tmpItem2 = items.OrderByDescending(p => p.MinVersion).FirstOrDefault(p => Version >= p.MinVersion);

                if (tmpItem == null)
                    return tmpItem2;
                if (tmpItem2 == null)
                    return tmpItem;

                return tmpItem.MaxVersion > tmpItem2.MinVersion ? tmpItem : tmpItem2;
            }

            protected override DbProviderFactory DbProviderFactory
            {
                get
                {
                    return OracleClientFactory.Instance;
                }
            }
        }

        class OracleCommand : DbCommand
        {
            internal  IDbCommand Command;
            internal OracleConnection _Connection;

            public OracleCommand(IDbCommand command)
            {
                Command = command;
                OracleClientFactory.SetBindByName(command, true);
                _Connection = Command.Connection as OracleConnection;
            }

            public override void Cancel()
            {
                Command.Cancel();
            }

            public override string CommandText
            {
                get
                {
                    return Command.CommandText;
                }
                set
                {
                    Command.CommandText = value;
                }
            }

            public override int CommandTimeout
            {
                get
                {
                    return Command.CommandTimeout;
                }
                set
                {
                    Command.CommandTimeout = value;
                }
            }

            public override CommandType CommandType
            {
                get
                {
                    return Command.CommandType;
                }
                set
                {
                    Command.CommandType = value;
                }
            }

            protected override DbParameter CreateDbParameter()
            {
                return new OracleParameter(Command.CreateParameter());
            }
           

            protected override DbConnection DbConnection
            {
                get
                {
                    return Command.Connection as DbConnection;
                }
                set
                {
                    Command.Connection = value;
                    _Connection = value as OracleConnection;
                }
            }

            protected override DbParameterCollection DbParameterCollection
            {
                get
                {
                    return new OracleDbParameterCollection(Command.Parameters);
                }
            }

            protected override DbTransaction DbTransaction
            {
                get
                {
                    return Command.Transaction as DbTransaction;
                }
                set
                {
                    Command.Transaction = value;
                }
            }

            public override bool DesignTimeVisible
            {
                get
                {
                    return false;
                }
                set
                {

                }
            }

            protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
            {
                return new OracleReader(Command.ExecuteReader(behavior));
            }

            public override int ExecuteNonQuery()
            {
                return Command.ExecuteNonQuery();
            }

            public override object ExecuteScalar()
            {
                return Command.ExecuteScalar();
            }

            public override void Prepare()
            {
                Command.Prepare();
            }

            public override UpdateRowSource UpdatedRowSource
            {
                get
                {
                    return Command.UpdatedRowSource;
                }
                set
                {
                    Command.UpdatedRowSource = value;
                }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    Command.Dispose();

                    if (Parameters != null)
                        Parameters.Clear();
                }
            }
        }

        class OracleTransaction : DbTransaction
        {
            private IDbTransaction Transaction;
            public OracleTransaction(IDbTransaction tran)
            {
                Transaction = tran;
            }
            public override void Commit()
            {
                Transaction.Commit();
            }

            protected override DbConnection DbConnection
            {
                get { return Transaction.Connection as DbConnection; }
            }

            public override IsolationLevel IsolationLevel
            {
                get { return Transaction.IsolationLevel; }
            }

            public override void Rollback()
            {
                Transaction.Rollback();
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    Transaction.Dispose();
            }
        }

        class OracleParameter : DbParameter
        {
            internal readonly IDbDataParameter Parameter;
            public OracleParameter(IDbDataParameter parameter)
            {
                Parameter = parameter;
            }

            public override DbType DbType
            {
                get
                {
                    return Parameter.DbType;
                }
                set
                {
                    Parameter.DbType = value;
                }
            }

            public override ParameterDirection Direction
            {
                get
                {
                    return Parameter.Direction;
                }
                set
                {
                    Parameter.Direction = value;
                }
            }

            public override bool IsNullable
            {
                get
                {
                    return Parameter.IsNullable;
                }
                set
                {
                    Parameter.SetProperty("IsNullable", value);
                }
            }

            public override string ParameterName
            {
                get
                {
                    return Parameter.ParameterName;
                }
                set
                {
                    Parameter.ParameterName = value;
                }
            }

            public override void ResetDbType()
            {
            }

            public override int Size
            {
                get
                {
                    return Parameter.Size;
                }
                set
                {
                    Parameter.Size = value;
                }
            }

            public override string SourceColumn
            {
                get
                {
                    return Parameter.SourceColumn;
                }
                set
                {
                    Parameter.SourceColumn = value;
                }
            }

            public override bool SourceColumnNullMapping
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            public override DataRowVersion SourceVersion
            {
                get
                {
                    return Parameter.SourceVersion;
                }
                set
                {
                    Parameter.SourceVersion = value;
                }
            }

            public override object Value
            {
                get
                {
                    return Parameter.Value;
                }
                set
                {
                    Parameter.Value = value;
                }
            }
        }

        class OracleDbParameterCollection : DbParameterCollection
        {
            private static Type ItemType;
            static OracleDbParameterCollection()
            {
                ItemType = typeof(OracleParameter);
            }

            private IDataParameterCollection InnerList;
            public OracleDbParameterCollection(IDataParameterCollection innerList)
            {
                InnerList = innerList;
            }

            public override int Add(object value)
            {
                ValidateType(value);

                var p = CheckParameterName(value);

                return InnerList.Add(p.Parameter);
            }

            private OracleParameter CheckParameterName(object value)
            {
                var p = value as OracleParameter;
                if (string.IsNullOrEmpty(p.ParameterName))
                    p.ParameterName = "p" + InnerList.Count;
                return p;
            }

            private static void ValidateType(object value)
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                var type = value.GetType();

                if (!ItemType.IsAssignableFrom(type))
                    throw new ArgumentException("Invalid parameter type.");
            }

            public override void AddRange(Array values)
            {
                if (values == null || values.Length == 0)
                    throw new ArgumentNullException("values");

                for (int i = 0; i < values.Length; i++)
                    Add(values.GetValue(i));
            }

            public override void Clear()
            {
                InnerList.Clear();
            }

            public override bool Contains(string parameterName)
            {
                return InnerList.Contains(parameterName);
            }

            public override bool Contains(object value)
            {
                var p = value as OracleParameter;
                if (p != null)
                    return InnerList.Contains(p.Parameter);

                return InnerList.Contains(value);
            }

            public override void CopyTo(Array array, int index)
            {
                InnerList.CopyTo(array, index);
            }

            public override int Count
            {
                get { return InnerList.Count; }
            }

            public override System.Collections.IEnumerator GetEnumerator()
            {
                return InnerList.GetEnumerator();
            }

            protected override DbParameter GetParameter(string parameterName)
            {
                int index = this.IndexOf(parameterName);
                return GetParameter(index);
            }

            protected override DbParameter GetParameter(int index)
            {
                if (index < 0)
                {
                    throw new ArgumentException(index.ToString());
                }
                return new OracleParameter(this.InnerList[index] as IDbDataParameter);
            }

            public override int IndexOf(string parameterName)
            {
                return InnerList.IndexOf(parameterName);
            }

            public override int IndexOf(object value)
            {
                ValidateType(value);
                
                return InnerList.IndexOf((value as OracleParameter).Parameter);
            }

            public override void Insert(int index, object value)
            {
                ValidateType(value);
                var p = CheckParameterName(value);

                InnerList.Insert(index, p.Parameter);

            }

            public override bool IsFixedSize
            {
                get { return InnerList.IsFixedSize; }
            }

            public override bool IsReadOnly
            {
                get { return InnerList.IsReadOnly; }
            }

            public override bool IsSynchronized
            {
                get { return InnerList.IsSynchronized; }
            }

            public override void Remove(object value)
            {
                ValidateType(value);
                int index = this.IndexOf(value);
                this.RangeCheck(index);
                this.RemoveIndex(index);
            }

            public override void RemoveAt(string parameterName)
            {
                int index = this.IndexOf(parameterName);
                this.RangeCheck(index);
                this.RemoveIndex(index);
            }

            public override void RemoveAt(int index)
            {
                this.RangeCheck(index);
                this.RemoveIndex(index);
            }

            private void RemoveIndex(int index)
            {
                InnerList.RemoveAt(index);
            }

            protected override void SetParameter(string parameterName, DbParameter value)
            {
                int index = this.IndexOf(parameterName);
                this.Replace(index, value as OracleParameter);
            }

            protected override void SetParameter(int index, DbParameter value)
            {
                this.Replace(index, value as OracleParameter);
            }

            private void RangeCheck(int index)
            {
                if ((index < 0) || (this.Count <= index))
                {
                    throw new ArgumentException();
                }
            }

            private void Replace(int index, OracleParameter value)
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                this.RangeCheck(index);

                InnerList[index] = value.Parameter;
            }

            public override object SyncRoot
            {
                get { return InnerList.SyncRoot; }
            }
        }

        class OracleReader : DbDataReader
        {
            private IDataReader rs;
            public OracleReader(IDataReader reader)
            {
                rs = reader;
            }
            public override void Close()
            {
                rs.Close();
            }

            public override int Depth
            {
                get { return rs.Depth; }
            }

            public override int FieldCount
            {
                get { return rs.FieldCount; }
            }

            public override bool GetBoolean(int ordinal)
            {
                return rs.GetBoolean(ordinal);
            }

            public override byte GetByte(int ordinal)
            {
                return rs.GetByte(ordinal);
            }

            public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
            {
                return rs.GetBytes(ordinal,dataOffset,buffer,bufferOffset,length);
            }

            public override char GetChar(int ordinal)
            {
                return rs.GetChar(ordinal);
            }

            public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
            {
                return rs.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
            }

            public override string GetDataTypeName(int ordinal)
            {
                return rs.GetDataTypeName(ordinal);
            }

            public override DateTime GetDateTime(int ordinal)
            {
                return rs.GetDateTime(ordinal);
            }

            public override decimal GetDecimal(int ordinal)
            {
                return rs.GetDecimal(ordinal);
            }

            public override double GetDouble(int ordinal)
            {
                return rs.GetDouble(ordinal);
            }

            public override System.Collections.IEnumerator GetEnumerator()
            {
                return rs.Func<IEnumerator>("GetEnumerator");
            }

            public override Type GetFieldType(int ordinal)
            {
                return rs.GetFieldType(ordinal);
            }

            public override float GetFloat(int ordinal)
            {
                return rs.GetFloat(ordinal);
            }

            public override Guid GetGuid(int ordinal)
            {
                return rs.GetGuid(ordinal);
            }

            public override short GetInt16(int ordinal)
            {
                return rs.GetInt16(ordinal);
            }

            public override int GetInt32(int ordinal)
            {
                return rs.GetInt32(ordinal);
            }

            public override long GetInt64(int ordinal)
            {
                return rs.GetInt64(ordinal);
            }

            public override string GetName(int ordinal)
            {
                return rs.GetName(ordinal);
            }

            public override int GetOrdinal(string name)
            {
                return rs.GetOrdinal(name);
            }

            public override DataTable GetSchemaTable()
            {
                return rs.GetSchemaTable();
            }

            public override string GetString(int ordinal)
            {
                return rs.GetString(ordinal);
            }

            public override object GetValue(int ordinal)
            {
                return rs.GetValue(ordinal);
            }

            public override int GetValues(object[] values)
            {
                return rs.GetValues(values);
            }

            public override bool HasRows
            {
                get { throw new NotImplementedException(); }
            }

            public override bool IsClosed
            {
                get { return rs.IsClosed; }
            }

            public override bool IsDBNull(int ordinal)
            {
                return rs.GetBoolean(ordinal);
            }

            public override bool NextResult()
            {
                return rs.NextResult();
            }

            public override bool Read()
            {
                return rs.Read();
            }

            public override int RecordsAffected
            {
                get { return rs.RecordsAffected;  }
            }

            public override object this[string name]
            {
                get { return rs[name]; }
            }

            public override object this[int ordinal]
            {
                get { return rs[ordinal]; }
            }
        }

        //class OracleDataAdapter : DbDataAdapter, IDbDataAdapter
        //{
        //    private DbDataAdapter Inner;
        //    private IDbDataAdapter InnerAdapter;
        //    private Func<IDbCommand, int> AddToBatchFunc;

        //    public OracleDataAdapter(DbDataAdapter adapter)
        //    {
        //        Inner = adapter;
        //        InnerAdapter = adapter;
        //        var m = adapter.GetType().GetMethod("AddToBatch", BindingFlags.Instance | BindingFlags.NonPublic);
        //        AddToBatchFunc = (cmd) => (int)m.FastFuncInvoke(adapter, cmd);
        //    }

        //    IDbCommand IDbDataAdapter.DeleteCommand
        //    {
        //        get
        //        {
        //            return new OracleCommand(InnerAdapter.DeleteCommand);
        //        }
        //        set
        //        {
        //            var cmd = value as OracleCommand;
        //            if (cmd != null)
        //                InnerAdapter.DeleteCommand = cmd.Command;
        //        }
        //    }

        //    IDbCommand IDbDataAdapter.InsertCommand
        //    {
        //        get
        //        {
        //            return new OracleCommand(InnerAdapter.InsertCommand);
        //        }
        //        set
        //        {
        //            var cmd = value as OracleCommand;
        //            if (cmd != null)
        //                InnerAdapter.InsertCommand = cmd.Command;
        //        }
        //    }

        //    IDbCommand IDbDataAdapter.SelectCommand
        //    {
        //        get
        //        {
        //            return new OracleCommand(InnerAdapter.SelectCommand);
        //        }
        //        set
        //        {
        //            var cmd = value as OracleCommand;
        //            if (cmd != null)
        //                InnerAdapter.SelectCommand = cmd.Command;
        //        }
        //    }

        //    IDbCommand IDbDataAdapter.UpdateCommand
        //    {
        //        get
        //        {
        //            return new OracleCommand(InnerAdapter.UpdateCommand);
        //        }
        //        set
        //        {
        //            var cmd = value as OracleCommand;
        //            if (cmd != null)
        //                InnerAdapter.UpdateCommand = cmd.Command;
        //        }
        //    }

        //    protected override int AddToBatch(IDbCommand command)
        //    {
        //        return AddToBatchFunc(command);
        //    }

        //    protected override void ClearBatch()
        //    {
        //        base.ClearBatch();
        //    }

        //    protected override int ExecuteBatch()
        //    {
        //        return base.ExecuteBatch();
        //    }

        //}

        class OracleClientFactory : DbProviderFactory
        {
            static readonly ConstructorHandler ConnectionCreator;
            static readonly ConstructorHandler CommandCreator;
            static readonly ConstructorHandler ParameterCreator;
            static readonly ConstructorHandler AdapterCreator;

            internal static readonly Type OracleDbType;
            internal static readonly Setter SetOracleDbTypeProperty;
            internal static readonly Setter SetBindByName;


            static OracleClientFactory()
            {
                var classLoader = ServiceLocator.Get<IClassLoader>();
                var ConnectionType = classLoader.LoadType("Oracle.DataAccess.Client.OracleConnection,Oracle.DataAccess");
                var Assembly = ConnectionType.Assembly;
                var connCtor = ConnectionType.GetConstructor(Type.EmptyTypes);
                var cmdType = Assembly.GetType("Oracle.DataAccess.Client.OracleCommand");
                var cmdCtor = cmdType.GetConstructor(Type.EmptyTypes);
                var BindByNameProperty = cmdType.GetProperty("BindByName");
                var ParameterType = Assembly.GetType("Oracle.DataAccess.Client.OracleParameter");
                var ParameterCtor = ParameterType.GetConstructor(Type.EmptyTypes);
                var AdapterType = Assembly.GetType("Oracle.DataAccess.Client.OracleDataAdapter");
                var AdapterCtor = AdapterType.GetConstructor(Type.EmptyTypes);

                ConnectionCreator = connCtor.GetCreator();
                CommandCreator = cmdCtor.GetCreator();
                ParameterCreator = ParameterCtor.GetCreator();
                AdapterCreator = AdapterCtor.GetCreator();

                SetOracleDbTypeProperty = ParameterType.GetProperty("OracleDbType").GetSetter();
                OracleDbType = Assembly.GetType("Oracle.DataAccess.Client.OracleDbType");

                SetBindByName = BindByNameProperty.GetSetter();
            }

            private OracleClientFactory() { }
            public static readonly OracleClientFactory Instance = new OracleClientFactory();

            public override DbConnection CreateConnection()
            {
                return new OracleConnection((IDbConnection)ConnectionCreator(null));
            }

            public override DbCommand CreateCommand()
            {
                return new OracleCommand((IDbCommand)CommandCreator(null));
            }

            public override DbParameter CreateParameter()
            {
                return new OracleParameter((IDbDataParameter)ParameterCreator(null));
            }

            public override DbDataAdapter CreateDataAdapter()
            {
                return (DbDataAdapter)AdapterCreator(null);
            }
            
        }
    }
}
